package tasks;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.IPath;
import org.eclipse.jdt.core.IBuffer;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IExtendedModifier;
import org.eclipse.jdt.core.dom.MarkerAnnotation;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.internal.corext.dom.Bindings;
import org.eclipse.jdt.internal.ui.text.correction.ASTResolving;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.text.edits.UndoEdit;

public class ImplementTask implements ITask {
	
	private TypeDeclaration _superInterface = null;
	
	private TypeDeclaration _type = null;
	
	//private List<MethodInvocation> _methods = new LinkedList<MethodInvocation>();

	private boolean _isActive = true;
	
	private static String _OVERRIDE = "Override"; 
	
	private UndoEdit _undo = null;
	
	private CompilationUnit _compilationUnit = null;
	
	private ICompilationUnit _iCompilationUnit = null;
	
	private IDocument _document = null;

	private Error _lastError; 
	
	private static final String _taskName = "Implement Interface";	
	
	public ImplementTask(TypeDeclaration superInterface, TypeDeclaration type) {
		_superInterface = superInterface;
		_type = type;
		_isActive = true;
		_compilationUnit = ASTResolving.findParentCompilationUnit(_type);
		_iCompilationUnit = (ICompilationUnit)_compilationUnit.getJavaElement();
		try {
			_document = new Document(_iCompilationUnit.getSource());
		} catch (JavaModelException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	@Override
	public boolean isActive() {
		return _isActive;
	}
	
	private void markOverride(AST ast, MethodDeclaration method, TypeDeclaration typeDeclaration, ASTRewrite rewrite) {
		MarkerAnnotation markerAnnotation = ast.newMarkerAnnotation();
		markerAnnotation.setTypeName(ast.newSimpleName(_OVERRIDE));
		List<?> modifiers = method.modifiers();
		for (Object m:modifiers)
			if (((IExtendedModifier)m).isAnnotation())
				/**Check whether the method is already marked as overridden*/
				if (((Annotation)m).getTypeName().getFullyQualifiedName().equals(_OVERRIDE))
					return;
		method.modifiers().add(0, markerAnnotation);
	}
	
	@Override
	public String toString() {
		return _superInterface.getName().getFullyQualifiedName()+" in class: "+_type.getName().getFullyQualifiedName()+".";
	}

	@Override
	public void perform(Map<String,String> params) {
		ASTRewrite rewriter = ASTRewrite.create(_compilationUnit.getAST());
		
		ListRewrite lRW = rewriter.getListRewrite(_compilationUnit, CompilationUnit.TYPES_PROPERTY);
		lRW.remove(_type, null);
		AST ast = _compilationUnit.getAST();
		String interfaceName = _superInterface.getName().getFullyQualifiedName();
		_type.superInterfaceTypes().add(
				ast.newSimpleType(
					ast.newSimpleName(interfaceName)
				)
			);
		
		
		for (MethodDeclaration method:_type.getMethods()) {
			boolean isInInterface = false;
			for (MethodDeclaration methodDeclaration:_superInterface.getMethods()) {
				if (Bindings.isSubsignature(methodDeclaration.resolveBinding(), method.resolveBinding())) {
					isInInterface = true;
					break;
				}
			}
			if (isInInterface)
				markOverride(ast, method, _type, rewriter);
		}
		lRW.insertLast(_type, null);
		TextEdit edits = rewriter.rewriteAST(_document, _iCompilationUnit.getJavaProject().getOptions(true));
	
		// computation of the new source code
		try {
			_undo = edits.apply(_document);
		} catch (MalformedTreeException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (BadLocationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		// update of the compilation unit
		try {
			IBuffer buffer = _iCompilationUnit.getBuffer();
			buffer.setContents(_document.get());
			buffer.save(null, true);
			//_iCompilationUnit.reconcile();
			_iCompilationUnit.reconcile(ICompilationUnit.NO_AST,true,true,null,null);
		} catch (JavaModelException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		_isActive = false;
		System.out.println("ImplementsTask>ImplementsTask perform ended");		
	}

	@Override
	public void undo() {
		try {
			_undo.apply(_document);
		} catch (MalformedTreeException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (BadLocationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		// update of the compilation unit
		try {
			_iCompilationUnit.getBuffer().setContents(_document.get());
		} catch (JavaModelException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		_isActive = true;
		System.out.println("ImplementsTask>ImplementsTask perform rolled back");	
	}

	@Override
	public String getDescription() {
		return this.toString();
	}

	@Override
	public Error getLastError() {
		return _lastError;
	}
	
	private ICompilationUnit getIcompilationUnit() {
		IJavaElement iCompilationUnitOfSourceClass = _type.resolveBinding().getJavaElement(); 
		while (iCompilationUnitOfSourceClass.getElementType() != IJavaElement.COMPILATION_UNIT)
			iCompilationUnitOfSourceClass = iCompilationUnitOfSourceClass.getParent();
		return (ICompilationUnit)iCompilationUnitOfSourceClass;
	}

	@Override
	public String getTargetModuleName() {
		return getIcompilationUnit().getElementName();
	}

	@Override
	public Map<String, String> getParameters() {
		return new HashMap<String, String>();
	}

	@Override
	public String getTaskName() {
		return _taskName;
	}

	@Override
	public IPath getPath() {
		return getIcompilationUnit().getPath();
	}

}
